package com.thinkit.cms.service.content;

import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.thinkit.cms.api.category.CategoryModelRelationService;
import com.thinkit.cms.api.category.CategoryService;
import com.thinkit.cms.api.content.ContentAttachService;
import com.thinkit.cms.api.content.ContentAttrService;
import com.thinkit.cms.api.content.ContentRelatedService;
import com.thinkit.cms.api.content.ContentService;
import com.thinkit.cms.api.model.ModelService;
import com.thinkit.cms.api.site.SiteService;
import com.thinkit.cms.api.tag.TagService;
import com.thinkit.cms.config.ActuatorComponent;
import com.thinkit.cms.dto.category.CategoryDto;
import com.thinkit.cms.dto.content.*;
import com.thinkit.cms.dto.model.ModelDto;
import com.thinkit.cms.dto.tag.TagDto;
import com.thinkit.cms.entity.content.Content;
import com.thinkit.cms.mapper.content.ContentMapper;
import com.thinkit.cms.strategy.checker.ContentChecker;
import com.thinkit.cms.strategy.checker.PublishChecker;
import com.thinkit.core.base.BaseContextKit;
import com.thinkit.core.base.BaseServiceImpl;
import com.thinkit.core.constant.Channel;
import com.thinkit.core.constant.Constants;
import com.thinkit.core.handler.CustomException;
import com.thinkit.nosql.base.BaseRedisService;
import com.thinkit.nosql.base.BaseSolrService;
import com.thinkit.nosql.enums.SolrCoreEnum;
import com.thinkit.processor.channel.ChannelThreadLocal;
import com.thinkit.processor.message.MessageSend;
import com.thinkit.utils.enums.*;
import com.thinkit.utils.model.*;
import com.thinkit.utils.properties.ThinkItProperties;
import com.thinkit.utils.utils.Checker;
import com.thinkit.utils.utils.ModelFieldUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * <p>
 * 内容 服务实现类
 * </p>
 *
 * @author lg
 * @since 2020-08-15
 */
@Service
public class ContentServiceImpl extends BaseServiceImpl<ContentDto, Content, ContentMapper> implements ContentService {

    @Autowired
    CategoryService categoryService;

    @Autowired
    ContentAttrService contentAttrService;

    @Autowired
    ContentAttachService attachService;

    @Autowired
    ModelService modelService;

    @Autowired
    MessageSend messageSend;

    @Autowired
    SiteService siteService;

    @Autowired
    CategoryModelRelationService modelRelationService;

    @Autowired
    ContentRelatedService relatedService;

    @Autowired
    TagService tagService;

    @Autowired
    BaseRedisService baseRedisService;

    @Autowired
    ActuatorComponent actuatorComponent;

    @Autowired
    BaseSolrService baseSolrService;

//    @Autowired
//    ArticleService articleService;

    @Autowired
    ThinkItProperties thinkItProperties;

    @Override
    public PageDto<ContentDto> listPage(PageDto<ContentDto> pageDto){
        filterCategory(pageDto);
        IPage<ContentDto> pages = new Page<>(pageDto.getPageNo(), pageDto.getPageSize());
        IPage<ContentDto> result = baseMapper.listPage(pages, pageDto.getDto());
        if(Checker.BeNotEmpty(result.getRecords()))filterCover(result.getRecords());
        PageDto<ContentDto> resultSearch = new PageDto(result.getTotal(), result.getPages(), result.getCurrent(), Checker.BeNotEmpty(result.getRecords()) ? result.getRecords() : Lists.newArrayList());
        return resultSearch;
    }

    @Override
    public PageDto<ContentDto> pageRecycler(PageDto<ContentDto> pageDto) {
        filterCategory(pageDto);
        IPage<ContentDto> pages = new Page<>(pageDto.getPageNo(), pageDto.getPageSize());
        IPage<ContentDto> result = baseMapper.pageRecycler(pages, pageDto.getDto());
        if(Checker.BeNotEmpty(result.getRecords()))filterCover(result.getRecords());
        PageDto<ContentDto> resultSearch = new PageDto(result.getTotal(), result.getPages(), result.getCurrent(), Checker.BeNotEmpty(result.getRecords()) ? result.getRecords() : Lists.newArrayList());
        return resultSearch;
    }


    private void filterCover(List<ContentDto> contents){
        for(ContentDto content:contents){
            if(Checker.BeNotBlank(content.getCoverStr())){
                content.setCover(JSONUtil.toList(JSONUtil.parseArray(content.getCoverStr()),Map.class));
            }
        }
    }

    private void filterCategory(PageDto<ContentDto> pageDto){
        if (Checker.BeNotBlank(pageDto.getDto().getCategoryId())) {
            List<String> childcategoryIds = new ArrayList<>();
            childcategoryIds.add(pageDto.getDto().getCategoryId());
            List<String> newChildCategoryIds = new ArrayList<>();
            recursionCategoryIds(childcategoryIds, newChildCategoryIds);
            pageDto.getDto().setCategoryIds(newChildCategoryIds);
        }
    }


    private void recursionCategoryIds(List<String> childcategoryIds, List<String> newChildcategoryIds) {
        if (Checker.BeNotEmpty(childcategoryIds)) {
            newChildcategoryIds.addAll(childcategoryIds);
            for (String categoryId : childcategoryIds) {
                if (Checker.BeNotBlank(categoryId)) {
                    List<String> childCategs = getCategoryChildByParentId(categoryId);
                    recursionCategoryIds(childCategs, newChildcategoryIds);
                }
            }
        }
    }

    private List<String> getCategoryChildByParentId(String categoryId) {
        List<String> categoryIds = new ArrayList<>();
        List<CategoryDto> cmsCategoryDtos = categoryService.listCategoryByPid(categoryId);
        if (Checker.BeNotEmpty(cmsCategoryDtos)) {
            for (CategoryDto cmsCategoryDto : cmsCategoryDtos) {
                categoryIds.add(cmsCategoryDto.getId());
            }
        }
        return categoryIds;
    }

    @Override
    public  IPage<ContentDto>  pageContentForNoSql(Integer pageNo, Integer pageSize) {
        IPage<Map<String,String>> pages = new Page<>(pageNo, pageSize);
        IPage<ContentDto> result = baseMapper.pageContentForNoSql(pages, getSiteId());
        if (Checker.BeNotEmpty(result.getRecords())) {
             // 处理格式化
             for(ContentDto contentDto:result.getRecords()){
                 if(Checker.BeNotBlank(contentDto.getAttrData())){
                     Map<String,Object> params = ModelFieldUtil.jsonStrToMap(contentDto.getAttrData());
                     contentDto.setParams(params);
                 }
                 if(Checker.BeNotBlank(contentDto.getCoverStr())){
                     contentDto.setCover(JSONUtil.toList(JSONUtil.parseArray(contentDto.getCoverStr()),Map.class));
                 }
             }
             return result;
        }
        return null;
    }

    @Override
    public Tree<CategoryDto> treeCategory() {
        return categoryService.treeCategoryForContent();
    }

    @Override
    public List<DynamicModel> getFormDesign(String modelId) {
        ModelDto model = modelService.getModel(modelId);
        Boolean hasfile =model.getHasFiles();
        List<DynamicModel> dynamicModels=ModelFieldUtil.jsonStrToModel(model.getAllFieldList());
        if(hasfile){
            List<DynamicModel>  attachField = ModelFieldUtil.loadModel(BelongEnum.ATTACH);
            if(Checker.BeNotEmpty(attachField)){
                dynamicModels.addAll(attachField);
            }
        }
        return dynamicModels;
    }

    @Transactional
    @Override
    public void save(ContentDto v) {
        v.setStatus(StatusEnum.DRAFT.getCode()).setId(id());
        insertUpdateAttr(v,true);
        Content content = formatIt(v,true);
        baseMapper.insert(content);
        actuatorComponent.execute(v.getCategoryId(),ActionEnum.CREATE_CONTENT,v);
    }

    @Override
    public void reStaticBatchGenCid(List<String> ids) {
       if(Checker.BeNotEmpty(ids)){
           List<Map<String,Object>> maps = baseMapper.buildParams(ids);
           if(Checker.BeNotEmpty(maps)){
               maps.forEach(map->{
                   messageSend.sendMessage(ChannelEnum.CONTENT,Channel.NOTIFY_IT,map,true);
               });
           }
       }
    }




    @Transactional
    @Override
    public void update(ContentDto v) {
        insertUpdateAttr(v,false);
        Content content = formatIt(v,false);
        baseMapper.updateById(content);
        actuatorComponent.execute(v.getCategoryId(),ActionEnum.UPDATE_CONTENT,v,new ContentChecker(v));
    }

    private Content formatIt(ContentDto v,boolean isSave){
        String cover ="";
        if(Checker.BeNotEmpty(v.getCover())){
            cover =JSONUtil.toJsonStr(v.getCover());
        }
        CategoryDto categoryDto=categoryService.getDetailById(v.getCategoryId());
        if(isSave){
            String destPath = PathRule.getPathRule(categoryDto.getPathRule()).format(categoryDto.getCode(),v.getId())+
            Constants.DEFAULT_HTML_SUFFIX;
            v.setUrl(destPath).setPathRule(categoryDto.getPathRule());
        }else{
            String pathRule =baseMapper.getPathRule(v.getId());
            boolean hasChange =categoryDto.getPathRule().equals(pathRule);
            boolean changeCateCode = categoryDto.getCode().equals(v.getCategoryCode());
            if(!hasChange || !changeCateCode){
                String destPath = PathRule.getPathRule(categoryDto.getPathRule()).format(categoryDto.getCode(),v.getId())+
                Constants.DEFAULT_HTML_SUFFIX;
                v.setUrl(destPath).setPathRule(categoryDto.getPathRule());
            }
        }
        v.setCreateId(getUserId()).setGmtCreate(new Date());
        Content content =D2T(v);
        if(Checker.BeNotBlank(cover)){
            content.setCover(cover);
        }
        String pcategoryId = categoryDto.getContainChild()? categoryDto.getParentId():"";
        content.setPCategoryId(pcategoryId);
        return content;
    }

    @Transactional
    @Override
    public boolean delete(String id,boolean realDel) {
        ContentDto content = baseMapper.getInfo(id);
        if(Checker.BeNotNull(content)){
            content.setRealDel(realDel);
            String categoryId = content.getCategoryId();
            if(realDel){
                delAboutContet(id);
                String domain=siteService.getDomainCode(getSiteId());
                if(Checker.BeNotNull(domain)){
                    String finalPath = thinkItProperties.getSitePath()+domain+content.getUrl();
                    FileUtil.del(finalPath);
                }
            }else{
                baseMapper.updateStatus(id, StatusEnum.DELETE.getCode());
            }
            Map<String,Object> params = categoryService.loadTempParams(categoryId);
            actuatorComponent.execute(categoryId,ActionEnum.DELETE_CONTENT,params,new ContentChecker(content));
        }
        return true;
    }

    @Override
    public void recyclerByPks(List<String> ids) {
       if(Checker.BeNotEmpty(ids)){
           ids.forEach(id->{
               baseMapper.updateStatus(id, StatusEnum.DRAFT.getCode());
           });
       }
    }

    @Override
    public ApiResult viewContent(String id) {
        String url = baseMapper.findUrl(id);
        Map<String,Object> objectMap = siteService.getDomainCode(false,getSiteId());
        return ApiResult.result(objectMap.get("dt").toString()+url);
    }


    @Transactional
    @Override
    public ApiResult move(String categoryId, List<String> contentIds) {
        if (Checker.BeNotEmpty(contentIds) && Checker.BeNotBlank(categoryId)) {
            List<String> categoryIds = baseMapper.listCategoryByCids(contentIds);
            baseMapper.updateCategoryId(categoryId,contentIds);
            categoryIds.add(categoryId);
            for(String cid:categoryIds){
                actuatorComponent.execute(cid,ActionEnum.BATCH_MOVE,cid);
            }
        }
        return ApiResult.result();
    }

    @Override
    public void jobPublish(List<String> ids, Date date) {
        baseMapper.jobPublish(ids,date);
    }

    @Override
    public String queryCategoryId(String contentId) {
        return baseMapper.getCategoryId(contentId);
    }


    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    @Override
    public Map<String, Object> nextPrevious(String id, Boolean isNext, String categoryId) {
        return baseMapper.nextPrevious(id,isNext,categoryId);
    }

    @Override
    public void deleteAllByCategoryId(String categoryId, List<String> contentIds) {
        contentAttrService.deleteByFiled("content_id",contentIds);
        attachService.deleteByFiled("content_id",contentIds);
        relatedService.deleteByFiled("content_id",contentIds);
        super.deleteByFiled("category_id",categoryId);
    }

    @Override
    public Map<String,Object> analysisMonthTop(ContentAnalysisDto contentAnalysis) {
        Map<String,Object> params = new HashMap<>();
        List<ContentAnalysisDto> contentAnalysisDtos =  baseMapper.analysisMonthTop(contentAnalysis.getSiteId(),
                contentAnalysis.getStatus(),contentAnalysis.getYear());
        List<ContentAnalysisDto> categoryTop = baseMapper.analysisCategoryTop(contentAnalysis.getSiteId());
        params.put("publishData",contentAnalysisDtos);
        params.put("categoryData",categoryTop);
        return params;
    }

    @Override
    public Long viewClicks(String cid) {
        long clicks = 1;
        String key = Constants.CONTENT_VIEW_TIMES+cid;
        Boolean hasKey=baseRedisService.hasKey(key);
        if(!hasKey){
            Long viewTimes= baseMapper.viewTimes(cid);
            if(Checker.BeNull(viewTimes)){
                return 0L;
            }
            clicks=viewTimes+1;
            baseRedisService.set(key,clicks);
        }else{
            clicks= baseRedisService.increment(key, 1);
        }
        return clicks;
    }

    @Override
    public ApiResult viewLikes(String cid, String userAgent) {
        long likes = 1;
        String key = Constants.CONTENT_LIKE_TIMES+cid;
        String userAgentKey = Constants.CONTENT_LIKE_TIMES+userAgent+":"+cid;
        Boolean hasAgentKey=baseRedisService.hasKey(userAgentKey);
        if(hasAgentKey){
            return ApiResult.result(20040);
        }
        Boolean hasKey=baseRedisService.hasKey(key);
        if(!hasKey){
            Long likeTimes= baseMapper.viewLikes(cid);
            if(Checker.BeNull(likeTimes)){
                return ApiResult.result(20041);
            }
            likes = likeTimes+1;
            baseRedisService.set(key,likes);
        }else{
            likes= baseRedisService.increment(key, 1);
        }
        baseRedisService.set(userAgentKey,likes,10800L);
        return ApiResult.result(likes);
    }

    @Override
    public void syncViewLikes() {
        // clicks: views |  give_likes:likes
        Set<String> likeKeys = baseRedisService.getKeysBlear(Constants.CONTENT_LIKE_TIMES+"*"); // 点赞
        Set<String> viewKeys = baseRedisService.getKeysBlear(Constants.CONTENT_VIEW_TIMES+"*"); // 浏览
        if(Checker.BeNotEmpty(likeKeys)){
            for(String likeKey:likeKeys){
                Integer likes = (Integer) baseRedisService.get(likeKey);
                String id = likeKey.replace(Constants.CONTENT_LIKE_TIMES,"");
                baseMapper.updateViewLikes("give_likes",likes,id);
                // baseRedisService.remove(likeKey);
            }
        }
        if(Checker.BeNotEmpty(viewKeys)){
            for(String viewKey:viewKeys){
                String id = viewKey.replace(Constants.CONTENT_VIEW_TIMES,"");
                Integer views = (Integer) baseRedisService.get(viewKey);
                baseMapper.updateViewLikes("clicks",views,id);
                baseRedisService.remove(viewKey);
            }
        }
    }

    @Override
    public PageDto<SolrDocument> searchKeyWord(PageDto<SolrSearchModel> pageDto) {
        try {
            if (Checker.BeBlank(pageDto.getDto().getSiteId())) {
                throw new CustomException(ApiResult.result(5022));
            }
            return baseSolrService.querySolr(SolrCoreEnum.DEFAULT_CORE,pageDto);
        } catch (IOException|SolrServerException e) {
            log.error("solr 查询失败");
        }
        return new PageDto(0L,pageDto.getPageSize(),pageDto.getPageNo(),Lists.newArrayList());
    }

    @Override
    public List<ContentDto> listContents(List<String> noStoreIds,String status) {
        if(Checker.BeEmpty(noStoreIds)) return  Lists.newArrayList();
        List<ContentDto> contentDtos = baseMapper.listContents(noStoreIds);
        if(Checker.BeNotEmpty(contentDtos)){
            for(ContentDto contentDto:contentDtos){
                contentDto.setStatus(status);
               if(Checker.BeNotBlank(contentDto.getAttrData())){
                   Map<String,Object> params = ModelFieldUtil.jsonStrToMap(contentDto.getAttrData());
                   contentDto.setParams(params);
               }
               if(Checker.BeNotBlank(contentDto.getCoverStr())){
                   contentDto.setCover(JSONUtil.toList(JSONUtil.parseArray(contentDto.getCoverStr()),Map.class));
               }
            }
        }
        return Checker.BeNotEmpty(contentDtos)?contentDtos:Lists.newArrayList();
    }

    @Override
    public Map<String, Object> wechatArticle(String url,String modelId,Integer type,String cookie) {
        Map<String, Object> map = new HashMap<>();
        List<DynamicModel> dynamicModels = getFormDesign(modelId);

        return map;
    }

    private void delAboutContet(String contentId){
        contentAttrService.deleteByFiled("content_id",contentId);
        attachService.deleteByFiled("content_id",contentId);
        relatedService.deleteByFiled("content_id",contentId);
        super.deleteByPk(contentId);
    }

    @Override
    public void deletes(List<String> ids,boolean realDel) {
        if(Checker.BeNotEmpty(ids)){
            for(String id :ids){
                delete(id,realDel);
            }
        }
    }


    // @Cacheable(value= Constants.cacheName, key="#root.targetClass+'.'+#root.methodName+'.'+#p0.contentId",unless="#result == null")
    @Transactional(isolation =  Isolation.READ_UNCOMMITTED)
    @Override
    public Map<String, Object> loadTempParams(Map<String, Object> params, List<String> status) {
          String destPath =siteService.getDestPath(true,getSiteId(true));
          String templatePath=modelRelationService.loadTemplatePath(params);
          String contentId = params.get(Channel.CONTENT_ID).toString();
          Map<String, Object> map = baseMapper.loadTempParams(contentId,status);
          if(map!=null){
              setTags(map);
              setAttach(map,contentId);
              ModelFieldUtil.formatCover(map,true);
              ModelFieldUtil.formatData(map);
              map.put(Channel.DEST_PATH,destPath);
              map.put(Channel.TEMP_PATH,templatePath);
              return map;
          }
          return new HashMap<>();
    }

    @Override
    public void loadTempParams(List<Map<String, Object>> params, List<String> status) {
        String destPath =siteService.getDestPath(true,getSiteId(true));
        List<String> cids = new ArrayList<>();
        for(Map map:params){
            String contentId = map.get(Channel.CONTENT_ID).toString();
            if(!cids.contains(contentId)){
                cids.add(contentId);
            }
        }
        Map<String, Object> attachs=getAttachs(cids);
        for(Map map:params){
            String contentId = map.get(Channel.CONTENT_ID).toString();
            String templatePath=modelRelationService.loadTemplatePath(map);
            setAttachs(map,attachs,contentId);
            ModelFieldUtil.formatCover(map,true);
            ModelFieldUtil.formatData(map);
            map.put(Channel.DEST_PATH,destPath);
            map.put(Channel.TEMP_PATH,templatePath);
        }
    }

    private Map<String, Object>  getAttachs(List<String> contentIds){
        Map<String,Object> maps = new HashMap();
        List<Map> attachs = attachService.listAttachs(contentIds);
        if(Checker.BeNotEmpty(attachs)){
            for(Map attach:attachs){
                if(maps.containsKey(attach.get(Channel.CONTENT_ID))){
                    List<Map> attachList = ( List<Map>) maps.get(Channel.CONTENT_ID);
                    attachList.add(attach);
                }else{
                    List<Map> list = new ArrayList<>();
                    list.add(attach);
                    maps.put(attach.get(Channel.CONTENT_ID).toString(),attach);
                }
            }
        }
        return maps;
    }

    private void setAttachs(Map<String, Object> map,Map<String, Object> attachs,String contentId){
        Object attach = attachs.get(contentId);
        map.put(Channel.ATTACH,Checker.BeNotNull(attach)?attach:Lists.newArrayList());
    }

    private void setAttach(Map<String, Object> map,String contentId){
        List<Map> attach = attachService.listAttach(contentId);
        map.put(Channel.ATTACH,Checker.BeNotEmpty(attach)?attach:Lists.newArrayList());
    }

    private void setTags( Map<String, Object> map){
        boolean hasTags = Checker.BeNotNull(map) && !map.isEmpty() && map.containsKey(Channel.TAG_NAME);
        boolean hasTopTag = Checker.BeNotNull(map) && !map.isEmpty() && map.containsKey(Channel.TOP_TAG);
        if(hasTags){
            Object tagObs = map.get(Channel.TAG_NAME);
            if(Checker.BeNotNull(tagObs)){
                List<String> tagNames = Arrays.asList(tagObs.toString().split(","));
                map.put(Channel.TAG_NAME,Checker.BeNotEmpty(tagNames)?tagNames:Lists.newArrayList());
            }
        }else{
                map.put(Channel.TAG_NAME,Lists.newArrayList());
        }
        if(hasTopTag){
            Object topTag = map.get(Channel.TOP_TAG);
            if(Checker.BeNotNull(topTag)){
                List<String> topTagNames = Arrays.asList(topTag.toString().split(","));
                map.put(Channel.TOP_TAG,Checker.BeNotEmpty(topTagNames) ? topTagNames:Lists.newArrayList());
            }
        }else{
                map.put(Channel.TOP_TAG,Lists.newArrayList());
        }
    }

    @Override
    public void top(ContentDto content) {
        if(Checker.BeNotEmpty(content.getTopTags())){
            content.setTopTag(StringUtils.join(content.getTopTags(),","));
        }
        baseMapper.top(content);
        actuatorComponent.execute(content.getCategoryId(),ActionEnum.TOP_CONTENT,content);
    }

    @Override
    public Set<String> getTopTag() {
        List<String> topTags=baseMapper.getTopTag(getSiteId());
        if(Checker.BeEmpty(topTags)){
            topTags=Lists.newArrayList();
        }
        Set<String> tags = new HashSet<>(topTags);
        return tags;
    }

    // @Transactional
    @Override
    public void publish(PublishDto publishDto,boolean byJob) {
        List<String> ids = publishDto.getIds();
        Set<String> categoryIds = listCategoryByCids(ids);
        String status = publishDto.getStatus();
        if(Arrays.asList(StatusEnum.DRAFT.getCode(),StatusEnum.PUBLISH.getCode()).contains(status)){
            Integer count = baseMapper.publish(ids,status,publishDto.getDate(),byJob);
            Object siteId = ChannelThreadLocal.get(Channel.SITE_ID);
            genOperaFile(count,ids,publishDto.getRows(),categoryIds,status,siteId);
        }
    }

    private void genOperaFile(Integer count,List<String> ids,List<ContentDto> rows,
                              Set<String> categoryIds,String status, Object siteId){
        CompletableFuture.runAsync(()->{
            if(count>0){
                try {
                    Thread.sleep(500);
                    if(Checker.BeNotNull(siteId)){
                        BaseContextKit.setSiteId( siteId.toString());
                    }
                    if(Checker.BeNotEmpty(ids)){
                        for(String categoryId:categoryIds){
                            actuatorComponent.execute(categoryIds,ActionEnum.PUBLISH_CONTENT,categoryId,
                                    new PublishChecker(status,ids,ActionEnum.PUBLISH_CONTENT,rows));
                        }
                    }
                } catch (InterruptedException e) {
                   log.error("系统异常:"+e.getMessage());
                }
            }
        });
    }



    @Override
    public Map<String,Object>  getDetail(String id) {
        Map<String,Object> map =new HashMap<>();
        List<DynamicModel> dynamicModels = new ArrayList<>();
        Content content =baseMapper.selectById(id);
        if(Checker.BeNotNull(content)){
            dynamicModels = getFormDesign(content.getModelId());
            ContentAttrDto attrDto = contentAttrService.getByField("content_id",content.getId());
            ContentDto contentDto =formatCover(content);
            filterModelField(dynamicModels,contentDto,attrDto);
            map.put("content",contentDto);
        }
        map.put("models",dynamicModels);
        return map;
    }

    private ContentDto formatCover(Content content){
        String cover ="";
        if(Checker.BeNotBlank(content.getCover())){
            cover = content.getCover();
            content.setCover(null);
        }
        ContentDto contentDto = T2D(content);
        if(Checker.BeNotBlank(cover)){
            List<Map> covers = JSONUtil.toList(JSONUtil.parseArray(cover),Map.class);
            contentDto.setCover(covers);
        }
        return contentDto;
    }

    // 编辑时用
    private void filterModelField(List<DynamicModel> dynamicModels,ContentDto content, ContentAttrDto attr){
        if(Checker.BeNotBlank(attr.getText())){
            content.setText(attr.getText());
        }
        if(Checker.BeNotBlank(attr.getOrigin())){
            content.setOrigin(attr.getOrigin());
        }
        if(Checker.BeNotBlank(attr.getOriginUrl())){
            content.setOriginUrl(attr.getOriginUrl());
        }
        if(Checker.BeNotEmpty(dynamicModels)){
            ModelFieldUtil.setFieldVal(dynamicModels,content,true);
        }
        if(Checker.BeNotBlank(attr.getData())){
            List<DynamicModel> extendFields = ModelFieldUtil.jsonStrToModel(attr.getData());
            if(Checker.BeNotEmpty(extendFields)){
                ModelFieldUtil.copyField(dynamicModels,extendFields);
            }
        }
        DynamicModel attachField = checkHasField(dynamicModels,InputTypeEnum.INPUT_ATTACH,false);
        if(Checker.BeNotNull(attachField)){
            List<String> datas=attachService.listData(content.getId());
            if(Checker.BeNotEmpty(datas)){
                List<Map> value = new ArrayList<>();
                for(String data:datas){
                    value.add(JSONUtil.toBean(data,Map.class));
                }
                attachField.setDefaultValue(value);
            }
        }
        DynamicModel tagField = checkHasField(dynamicModels,InputTypeEnum.INPUT_TAG,false);
        if(Checker.BeNotNull(tagField) && Checker.BeNotBlank(content.getTagIds())){
            List<String> tags=tagService.listTags(Arrays.asList(content.getTagIds().split(",")));
            if(Checker.BeNotEmpty(tags)){
                tagField.setDefaultValue(tags);
            }
        }
    }

    private void insertUpdateAttr(ContentDto v,boolean isSave){
        List<DynamicModel> extendFields =findExtendField(v.getModelId(),v.getParams());
        DynamicModel attachField = checkHasField(extendFields,InputTypeEnum.INPUT_ATTACH,true);
        if(Checker.BeNotNull(attachField)){
            insertAttach(attachField,v.getId(),isSave);
            v.setHasFiles(true);
        }
        DynamicModel tagField = checkHasField(extendFields,InputTypeEnum.INPUT_TAG,true);
        if(Checker.BeNotNull(tagField)){
            insertTags(tagField,v);
            v.setHasTags(true);
        }
        if(isSave){
            setTags(v);
            insertAttr(v,extendFields);
        }else{
            setTags(v);
            updateAttr(v,extendFields);
        }
    }

    private void setTags(ContentDto v){
        if (Checker.BeNotEmpty(v.getTags())) {
            List<String> tagIds = tagService.saveTags(Arrays.asList(v.getTags().split(",")));
            if (Checker.BeNotEmpty(tagIds)) {
                v.setTagIds(StringUtils.join(tagIds.toArray(), ","));
            }
        }
    }

    private void updateAttr(ContentDto v,List<DynamicModel> extendFields){
        String attrText = v.getText();
        if(Checker.BeNotBlank(attrText)){
            attrText = this.emojiConverter.toHtml(attrText);
        }
        ContentAttrDto contentAttrDto =contentAttrService.getByField("content_id",v.getId());
        contentAttrDto.setText(attrText).setOrigin(v.getOrigin()).setOriginUrl(v.getOriginUrl());
        if(Checker.BeNotEmpty(extendFields)){
            contentAttrDto.setData(ModelFieldUtil.modelToJsonStr(extendFields));
        }
        contentAttrService.updateByPk(contentAttrDto);
    }

    private void insertAttr(ContentDto v,List<DynamicModel> extendFields){
        String attrText = v.getText();
        if(Checker.BeNotBlank(attrText)){
            attrText = this.emojiConverter.toHtml(attrText);
        }
        ContentAttrDto contentAttrDto =new ContentAttrDto();
        contentAttrDto.setContentId(v.getId()).setText(attrText).setOrigin(v.getOrigin()).setOriginUrl(v.getOriginUrl());
        if(Checker.BeNotEmpty(extendFields)){
            contentAttrDto.setData(ModelFieldUtil.modelToJsonStr(extendFields));
        }
        contentAttrService.insert(contentAttrDto);
    }

    private DynamicModel checkHasField(List<DynamicModel> extendFields,InputTypeEnum inputTypeEnum,boolean remove){
        DynamicModel attachModel = null;
        if(Checker.BeNotEmpty(extendFields)){
            Iterator<DynamicModel> iterator = extendFields.iterator();
            while (iterator.hasNext()) {
                DynamicModel model = iterator.next();
                boolean hasAttach = inputTypeEnum.getCode().equals(model.getInputType())
                && inputTypeEnum.getCode().equals(model.getFieldCode());
                if (hasAttach) {
                    attachModel = model;
                    if(remove){
                        iterator.remove();
                    }
                }
            }
        }
        return attachModel;
    }

    private void insertAttach(DynamicModel attachModel,String contentId,boolean isSave){
        if(Checker.BeNotNull(attachModel) && Checker.BeNotNull(attachModel.getDefaultValue())){
            List<Map> attachs = (List<Map>) attachModel.getDefaultValue();
            if(!isSave){
                attachService.deleteByFiled("content_id",contentId);
            }
            List<ContentAttachDto> contentAttachDtos =buildAttach(attachs,contentId);
            attachService.insertBatch(contentAttachDtos);
        }
    }

    private List<String> insertTags(DynamicModel tagsModel,ContentDto v){
        if(Checker.BeNotNull(tagsModel) && Checker.BeNotNull(tagsModel.getDefaultValue())){
            String tags = (String) tagsModel.getDefaultValue();
            List<TagDto> tagDtos =buildTags(tags,v);
            tagService.insertBatch(tagDtos);
        }
        return Lists.newArrayList();
    }

    private List<TagDto> buildTags(String tags,ContentDto v){
        List<TagDto> tagDtos =new ArrayList<>();
        List<String> tagIds =new ArrayList<>();
        if(Checker.BeNotBlank(tags)){
            String[] tagArr = tags.split(",");
            Map<String,String> tagsToMap = tagsToMap(tagArr);
            for(String tag:tagArr){
                if(!tagsToMap.containsKey(tag)){
                    String id = id();
                    TagDto tagDto = new TagDto();
                    tagDto.setName(tag).setId(id).setSiteId(getSiteId());
                    tagDtos.add(tagDto);
                    tagIds.add(id);
                }else{
                    tagIds.add(tagsToMap.get(tag));
                }
            }
        }
        if(Checker.BeNotEmpty(tagIds)){
            v.setTagIds(StringUtils.join(tagIds, ","));
        }
        return tagDtos;
    }

    private Map<String,String> tagsToMap(String[] tagArr){
        Map<String,String> map =new HashMap<>();
        List<TagDto> dbTags = tagService.listTags(Arrays.asList(tagArr),getSiteId());
        if(Checker.BeNotEmpty(dbTags)){
            for(TagDto tag:dbTags){
                map.put(tag.getName(),tag.getId());
            }
        }
        return map;
    }


    private List<ContentAttachDto> buildAttach(List<Map> attachs,String contentId){
        List<ContentAttachDto> attachDtos =new ArrayList<>();
        if(Checker.BeNotEmpty(attachs)){
            for(Map attachMap:attachs){
                ContentAttachDto attachDto = new ContentAttachDto();
                ModelFieldUtil.setFieldVal(attachDto,attachMap);
                attachDto.setContentId(contentId).setData(JSONUtil.toJsonStr(attachMap));
                attachDtos.add(attachDto);
            }
        }
        return attachDtos;
    }

    private List<DynamicModel>  findExtendField(String modelId,Map<String,Object> params){
        ModelDto model = modelService.getModel(modelId);
        String fieldStr=model.getAllFieldList();
        Boolean hasFile = model.getHasFiles();
        List<DynamicModel> extendField = new ArrayList<>();
        if(hasFile){
            List<DynamicModel> attachField = ModelFieldUtil.loadModel(BelongEnum.ATTACH);
            if(Checker.BeNotEmpty(attachField)){
                extendField.addAll(attachField);
            }
        }
        if(Checker.BeNotNull(params)&& !params.isEmpty()){
            List<DynamicModel> exdfields = ModelFieldUtil.listExtendModel(fieldStr);
            if(Checker.BeNotEmpty(exdfields)){
                extendField.addAll(exdfields);
            }
        }
        ModelFieldUtil.filterMapToSetFieldValue(extendField,params);
        return extendField;
    }


    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    @Override
    public Map<String, Object> listContent(Map<String,Object> map,Boolean notify) {
        pageIt(map,notify);
        return map;
    }


    @Override
    public List<Map<String, Object>> listByCode(String code, String categoryId,Integer num, String order) {
        List<String> asc = new ArrayList<>();
        List<String> desc = new ArrayList<>();
        if(Checker.BeNotBlank(order)){
            String[] orderFields = order.split(",");
            for(String orderField:orderFields){
                String [] arr = orderField.split("\\s+");
                if(arr[1].toLowerCase().equals("desc")){
                    desc.add(arr[0]);
                }else{
                    asc.add(arr[0]);
                }
            }
        }
        List<Map<String, Object>> contents = baseMapper.listByCode(getSiteId(),code,categoryId,num,asc,desc);
        formatContent(contents);
        setTags(contents);
        setAttachs(contents);
        return contents;
    }

    @Override
    public List<Map<String, Object>> listUpToDate(String code, String categoryId, Integer num, String order, Boolean publish) {
        List<String> asc = new ArrayList<>();
        List<String> desc = new ArrayList<>();
        if(Checker.BeNotBlank(order)){
            String[] orderFields = order.split(",");
            for(String orderField:orderFields){
                String [] arr = orderField.split("\\s+");
                if(arr[1].toLowerCase().equals("desc")){
                    desc.add(arr[0]);
                }else{
                    asc.add(arr[0]);
                }
            }
        }
        List<Map<String, Object>> contents = baseMapper.listUpToDate(getSiteId(),code,categoryId,num,asc,desc,publish);
        formatContent(contents);
        setTags(contents);
        setAttachs(contents);
        return contents;
    }


    @Override
    public Map<String, Object> contentById(String id) {
        Map<String,Object> content = baseMapper.loadTempParams(id, Arrays.asList("0","1"));
        setTags(content);
        setAttach(content,id);
        ModelFieldUtil.formatCover(content,true);
        ModelFieldUtil.formatData(content);
        return content;
    }

    @Override
    public Map<String, Object> nextPrevious(String id,String categoryId) {
        Map<String,Object> content = new HashMap<>();
        Map<String,Object> next = baseMapper.nextPrevious(id,true,categoryId);
        Map<String,Object> previous = baseMapper.nextPrevious(id,false,categoryId);
        content.put("nexts",next);
        content.put("previous",previous);
        return content;
    }

    @Override
    public Map<String, Object> pageContentParamsForAllGen(Integer pageNo,Integer pageSize) {
        Map<String, Object> result =new HashMap<>();
        List<Map<String, String>> rows = new ArrayList<>();
        Boolean hasNext = pageContent(pageNo,pageSize,getSiteId(),rows);
        result.put("rows",rows);
        result.put("hasNext",hasNext);
        return result;
    }


    @Override
    public Map<String, Object> categorySingleData(String categoryId, String code) {
        Map<String,Object> content = baseMapper.categorySingleData(categoryId, code,getSiteId());
        if(Checker.BeNotNull(content)){
            setTags(content);
            setAttach(content,content.get("id").toString());
            ModelFieldUtil.formatCover(content,true);
            ModelFieldUtil.formatData(content);
        }
        return content;
    }

    @Override
    public Set<String> listCategoryByCids(List<String> ids) {
        Set<String> sets = new HashSet<>();
        if(Checker.BeNotEmpty(ids)){
            List<String> categoryIds = baseMapper.listCategoryByCids(ids);
            if(Checker.BeNotEmpty(categoryIds)){
                sets = new HashSet<>(categoryIds);
            }
        }
        return sets;
    }

    @Override
    public Map<String, List> getTopNews(String categoryId, String code, String where) {
        Map<String,List> params = new HashMap<>();
        if(Checker.BeNotBlank(where)){
            String[] conditions = where.split(",");
            for(String condition:conditions){
                String [] carr = condition.split("\\s+");
                String field = carr[0];
                Integer num = carr.length==2?Integer.valueOf(carr[1]):10;
                List<String> codes = new ArrayList<>();
                if(Checker.BeNotBlank(code)){
                    codes =Arrays.asList(code.trim().split(","));
                }
                List<Map<String, Object>> contents = baseMapper.getTopNews(categoryId,codes,getSiteId(),field,num);
                ModelFieldUtil.formatDatas(contents);
                params.put(field,Checker.BeNotEmpty(contents)?contents:Lists.newArrayList());
            }
        }
        return params;
    }

    @Override
    public Map<String, List> getTopTags(String categoryId, String code, String where) {
        Map<String,List> params = new HashMap<>();
        if(Checker.BeNotBlank(where)){
            String[] conditions = where.split(",");
            for(String condition:conditions){
                String [] carr = condition.split("\\s+");
                String tag = carr[0];
                Integer num = carr.length==2?Integer.valueOf(carr[1]):10;
                List<String> codes = new ArrayList<>();
                if(Checker.BeNotBlank(code)){
                    codes =Arrays.asList(code.trim().split(","));
                }
                List<Map<String, List>> contents = baseMapper.getTopTags(categoryId,codes,getSiteId(),tag,num);
                ModelFieldUtil.formatCovers(contents,true);
                params.put(tag,Checker.BeNotEmpty(contents)?contents:Lists.newArrayList());
            }
        }
        return params;
    }

    @Override
    public void resetModelId(String categoryId) {
    }



    private Boolean pageContent(Integer pageNo,Integer pageSize,String siteId,List<Map<String, String>> rows) {
        IPage<Map<String,String>> pages = new Page<>(pageNo, pageSize);
        IPage<Map<String,String>> result = baseMapper.pageAllContentForGen(pages, siteId);
        if (result.getCurrent() <= result.getPages() && Checker.BeNotEmpty(result.getRecords())) {
            rows.addAll(result.getRecords());
        }
        if(pageNo.intValue()==result.getPages() || result.getTotal()==0){
            return false;
        }
        return true;
    }

    private void setTags(List<Map<String, Object>> contents){
        if(Checker.BeNotEmpty(contents)){
            for(Map<String, Object> content:contents){
                setTags(content);
            }
        }
    }

    private void setAttachs(List<Map<String, Object>> contents){
        if(Checker.BeNotEmpty(contents)){
            for(Map<String, Object> content:contents){
                String id = content.get("id").toString();
                setAttach(content,id);
            }
        }
    }

    private void pageIt(Map<String, Object> objectMap,Boolean notify){
        String  categoryId = objectMap.get("id").toString();
        Integer pageSize = (Integer) objectMap.get("pageSize");
        Integer pageNo = Checker.BeNull(objectMap.get("pageNo"))?1:(Integer) objectMap.get("pageNo");
        try {
            pageDatas(categoryId,pageSize,pageNo,objectMap,notify);
        }catch (Exception e){
            baseRedisService.remove(Channel.CACHE_CONTENT+categoryId);
            log.error(e.getMessage());
            throw e;
        }
    }

    private IPage<Map<String,Object>>  pageDatas(String  categoryId, Integer pageSize,Integer pageNo,Map<String,Object> params,Boolean notify){
        IPage<Map<String,Object>> result = cachePageDate(categoryId,pageSize,pageNo,params);
        setPage(result,params);
        setDestPath(params,pageNo);
        if(result.getCurrent()!= result.getPages() && result.getTotal()>0){
            Map<String,Object> tempParams = new HashMap<>();
            tempParams.putAll(params);
            tempParams.remove(Channel.CONTENTS);
            messageSend.sendMessage(ChannelEnum.CATEGORY_PAGE,Channel.NOTIFY_IT,tempParams,notify);
        }else{
            String key =   Channel.CACHE_CONTENT+categoryId;
            baseRedisService.remove(key);
        }
        return result;
    }

    /**
     * 优化查询速度快速查询
     * @return
     */
    private IPage<Map<String,Object>> cachePageDate(String  categoryId, Integer pageSize,Integer pageNo,Map<String,Object> objectMap){
        IPage<Map<String,Object>> resultPage = new Page<>(pageNo, pageSize);
        long maxRow=calculSuitable(1000,pageSize);
        int multiple=limitMultiple(maxRow,Long.valueOf(pageSize));
        String key = Channel.CACHE_CONTENT+categoryId;
        Boolean hasKey = baseRedisService.hasKey(key);
        if(!hasKey){
            int startNo = pageNo/multiple+1;
            IPage<Map<String,Object>> pages = new Page<>(startNo, maxRow);
            IPage<Map<String,Object>> result = baseMapper.pageContent(pages,categoryId,getSiteId());
            if(result.getTotal()>0 && Checker.BeNotEmpty(result.getRecords())){
                baseRedisService.rpList(key,result.getRecords());
            }
            resultPage.setTotal(result.getTotal()).setCurrent(pageNo);
            if(result.getTotal()%pageSize!=0){
                resultPage.setPages(result.getTotal()/pageSize+1);
            }else{
                resultPage.setPages(result.getTotal()/pageSize);
            }
            if(Checker.BeNotEmpty(result.getRecords())){
                int end = result.getRecords().size()>pageSize ? pageSize:result.getRecords().size();
                resultPage.setRecords(result.getRecords().subList(0,end));
            }
        }else{
            int startNo = pageNo%multiple;
            if(pageNo>1 && startNo==1){
                baseRedisService.remove(key);
                return cachePageDate(categoryId,pageSize,pageNo,objectMap);
            }else{// from redis
                int indexNo = 0;
                Long mapTotalPage = (Long) objectMap.get("totalPage");
                Long mapTotal =  (Long) objectMap.get("total");
                if(startNo!=0){
                    indexNo = (startNo-1) * pageSize;
                }else{
                    indexNo = (multiple-1) * pageSize;
                }
                List<Map<String,Object>> datas = baseRedisService.range(key,indexNo,indexNo+pageSize-1);
                resultPage.setRecords(datas).setPages(mapTotalPage).setTotal(mapTotal);
            }
        }
        return resultPage;
    }




    private int limitMultiple(Long maxRowRead,Long pageSize){
        Long multiple = (maxRowRead/pageSize);
        return multiple.intValue();
    }

    private long calculSuitable(long maxRow,long pageSize){
        long num = maxRow/pageSize;
        return num*pageSize;
    }



    private void setDestPath(Map<String,Object> params,Integer pageNo){
        String fristPath = params.get(Channel.FRIST_PATH).toString();
        String pathPrefix = fristPath.replace(Constants.DEFAULT_HTML_SUFFIX,"");
        if(pageNo>1){
            String destPathCopy = params.get(Channel.DEST_PATH_COPY).toString();
            String despath = destPathCopy.replace(Constants.DEFAULT_HTML_SUFFIX,"")+"_"+pageNo+Constants.DEFAULT_HTML_SUFFIX;
            String path = pathPrefix+"_"+pageNo+Constants.DEFAULT_HTML_SUFFIX;
            params.put(Channel.DEST_PATH,despath);
            params.put(Channel.PATH,path);
        }
        params.put(Channel.PATH_PREFIX,pathPrefix);
    }

    private void setPage(IPage<Map<String,Object>> result,Map<String, Object> objectMap){
        objectMap.put("pageNo",Long.valueOf(result.getCurrent()).intValue()+1);
        objectMap.put("pageSize",Long.valueOf(result.getSize()).intValue());
        objectMap.put("total",result.getTotal());
        objectMap.put("totalPage",result.getPages());
        objectMap.put("hasNext",result.getCurrent() != result.getPages());
        objectMap.put("contents",formatContent(result.getRecords()));
        if(result.getTotal()!=0){
            double percent = new BigDecimal(result.getCurrent()).
            divide(new BigDecimal(result.getPages()),2,BigDecimal.ROUND_HALF_UP).
            multiply(new BigDecimal(100)).doubleValue();
            objectMap.put( Channel.PERCENT,percent);
        }
    }

    private List<Map<String, Object>> formatContent(List<Map<String, Object>> contents){
        if(Checker.BeNotEmpty(contents)){
            for(Map map:contents){
                ModelFieldUtil.formatData(map);
                ModelFieldUtil.formatCover(map,true);
            }
            return contents;
        }
        return Lists.newArrayList();
    }

}
